﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NLite.Reflection;
using NLite.Messaging;
using NLite.Messaging.Internal;
using System.Reflection;

namespace NLite.Mini.Listener
{
    /// <summary>
    /// 
    /// </summary>
    public class SubscribeListener:ComponentListenerAdapter
    {
        /// <summary>
        /// 
        /// </summary>
        public SubscribeListener() : base(ComponentListenStage.PostCreation) { }

        static readonly MethodInfo Create2HandlerMethod = typeof(ObserverHandler).GetMethod("Create2", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
        static readonly MethodInfo Create1HandlerMethod = typeof(ObserverHandler).GetMethod("Create1", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);

        static readonly BindingFlags Flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
        static readonly Type MessageBusType = typeof(IMessageBus);
        static readonly Type Action1Type = typeof(Action<>);
        static readonly Type Action2Type = typeof(Action<,>);
        static readonly Type ActionType = typeof(Action);

        /// <summary>
        /// 
        /// </summary>
        /// <param name="ctx"></param>
        public override void OnPostCreation(NLite.Mini.Context.IPostCreationContext ctx)
        {
            var instance = ctx.Instance;
            if (instance == null || SubscribeListener.MessageBusType.IsAssignableFrom(instance.GetType()))
                return;

            var bus = Kernel.Get<IMessageBus>();
            if (bus == null)
                return;

            var subscribers = (from m in instance.GetType().GetMethods(SubscribeListener.Flags)
                               let attr = m.GetAttribute<SubscribeAttribute>(true)
                               let ps = m.GetParameters()
                               where m.ReturnType == Types.Void
                               where attr != null
                               where ps.Length == 2 || ps.Length == 1 || (ps.Length == 0 && !string.IsNullOrEmpty(attr.Topic))
                               select new { Method = m, Topic = attr.Topic, Mode = attr.Mode, Parameters = ps }).ToArray();

            var disCollector = instance as IDisposeCollector;
            var compositeDis = instance as ICompositeDisposable;

            foreach (var item in subscribers)
            {
                Type msgType = null;
                Type handlerType = null;
                Delegate handler = null;
                IObserverHandler<IMessage> messageHandler = null;

                if (item.Parameters.Length == 2)
                {
                    msgType = item.Parameters[1].ParameterType ;
                    handlerType =SubscribeListener.Action2Type.MakeGenericType(Types.Object, msgType);
                    handler = Delegate.CreateDelegate(handlerType, instance, item.Method);

                    messageHandler = (IObserverHandler<IMessage>)Create2HandlerMethod.MakeGenericMethod(msgType).FastFuncInvoke(null, handler);

                }
                else if (item.Parameters.Length == 1)
                {
                    msgType = item.Parameters[0].ParameterType;
                    handlerType = SubscribeListener.Action1Type.MakeGenericType(msgType);
                    handler = Delegate.CreateDelegate(handlerType, instance, item.Method);

                    messageHandler = (IObserverHandler<IMessage>)Create1HandlerMethod.MakeGenericMethod(msgType).FastFuncInvoke(null, handler);

                }
                else
                {
                    handler = Delegate.CreateDelegate(SubscribeListener.ActionType, instance, item.Method);
                    messageHandler = ObserverHandler.Create((Action)handler);
                }
              
                var unsubscriber = bus.Subscribe(item.Topic, msgType, item.Mode, messageHandler);
                if (compositeDis != null)
                    compositeDis.AddDisposable(unsubscriber);
                else if (disCollector != null)
                    disCollector.Disposes.AddDisposable(unsubscriber);
            }

        }
    }
}
